// ==UserScript==        
// @name           Help Forum Tweaks
// @namespace      http://google-help-forum-tweaks.googlecode.com/svn/trunk/help-forum-tweaks.user.js
// @description    Tweaks for the Google Help Forums
// @version        0.50
// @include        http://www.google.com/support/forum/p/*
// ==/UserScript==

/*
Copyright 2009 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/* Requires Chrome 2.x with user-scripts enabled
** or Mozilla FireFox with Greasemonkey Extensions.
**
** See http://dev.chromium.org/developers/design-documents/user-scripts
** or http://lifehacker.com/5180010/enable-user-scripts-in-google-chrome
**
** Make sure to put it in the right location, eg:
** C:\Documents and Settings\yourname\Local Settings\Application Data\
**     Google\Chrome\User Data\Default\User Scripts
** and restart Chrome with the option " --enable-user-scripts"
**
** SVN information (please _don't_ remove next line):
** $Id$
**
*/

var URL = window.location.href, ghfConfig = new Object; 
String.prototype.startsWith = function(s) { return this.indexOf(s)===0; }

// Google Help Forum view / tweak configuration.
// Feel free to adjust the settings to your personal preferences.
// Remember to restart Chrome when making changes.
// Firefox requires just a page reload.
function setGHFConfig() {
    // Threads marked as visited in your browser.
    ghfConfig.color_read_by_you    = "#EFEFEF";
    // Threads marked as _not_ visited in your browser.
    ghfConfig.color_read_by_others = "inherit" ; // or a color like "#E7EFFF";
    // Title font size. 13px defaults and doesn't hurt the layout, 
    // 15px is already large.
    ghfConfig.title_font_size      = "15px";
    // "true" or "false" to show thread titles in bold font.
    ghfConfig.thread_title_bold    = true;
    // 1 or 0 to show user votes in bold font. 
    // Note this is 0/1 to add further options.
    ghfConfig.votes_bold           = 0;
    // "true" or "false" to show side links to boards.
    ghfConfig.show_side_links      = true;
    // "true" or "false" to resize the edit field area for posts.
    ghfConfig.resize_editor        = true;
    // "true" or "false" to display last post dates as links.
    ghfConfig.link_to_last         = true;

    // "true" or "false" to keep track of read threads locally.
    ghfConfig.read_tracker         = true;

}

// Cookie set and get functions.
if( !GM_getValue && !GM_setValue && !unsafeWindow ) {
    function GM_setValue( cookieName, cookieValue, lifeTime ) {
        if ( !cookieName ) { return; }
        if ( lifeTime == "delete" ) { lifeTime = -10; }
                               else { lifeTime = 31536000; }
        document.cookie = escape( cookieName ) + "=" + escape( cookieValue ) +
                          ";expires=" + ( new Date( ( new Date() ).getTime() + 
                          ( 1000 * lifeTime ) ) ).toGMTString() + ";path=/";
    }

    function GM_getValue( cookieName, oDefault ) {
        var cookieJar = document.cookie.split( "; " );
        for( var x = 0; x < cookieJar.length; x++ ) {
            var oneCookie = cookieJar[x].split( "=" );
            if ( oneCookie[0] == escape( cookieName ) ) {
                try      { var footm = unescape(oneCookie[1]); } 
                catch(e) { return oDefault; }
                return footm;
            }
        }
        return oDefault;
    }

    function GM_registerMenuCommand( oText, oFunc ) {}

    window.unsafeWindow = window;
}

// Show the links in the sidebar
function showSideLinks(serialized_data) {
    var entries = serialized_data.split("\t");
    var side_ul = document.getElementById("wparoot");
    var new_li, new_a, i = 0;
    if (side_ul) {
        for (i=0; i<entries.length; i++) {
            var suburl = entries[i].match(/\"(.*?)\"/);
            var subtitle = entries[i].match(/>(.*?)</);
            var subclass = entries[i].match(/class="(.*?)"/);
            if (!subclass) {
                new_li = document.createElement("li");
                new_li.id = "new_li_" + i;
                new_li.className = "wpabaruhCSS";
                new_a = document.createElement("a");
                new_a.href = suburl[1];
                new_a.innerHTML = subtitle[1];
                new_li.appendChild(new_a);
                side_ul.appendChild(new_li);
            }
        }

        // Add a links to the thread overview on any other page. 
        if (!URL.match("browse")) { 
            var languageCode = URL.match("[\?&]hl=([^&#]*)")[1];
            var forumName = URL.match("/forum\/p\/([^/]*)[/\?]")[1];
            new_li = document.createElement("li");
            new_li.id = "new_li_" + (i+1);
            new_li.className = "wpabaruhCSS";
            new_a = document.createElement("a");
            new_a.href = "http://www.google.com/support/forum/p/" + forumName + "/browse?hl=" + languageCode;
            switch (languageCode) {
                case "de": new_a.innerHTML = "Alle Threads"; break;
                case "it": new_a.innerHTML = "Tutte le discussioni"; break;
                case "fr": new_a.innerHTML = "Toutes les discussions"; break;
                case "es": new_a.innerHTML = "Todas las discussiónes"; break;
                default:   new_a.innerHTML = "All Threads(2)"; // default
            }
            new_li.appendChild(new_a);
            side_ul.appendChild(new_li);
        }
    }
}

// Fix editor window: make larger, fix horizontal scrolling
function fixEditorTextField() {
    // Editor is made up of multiple frames, we must fix two of them
    try {
        if (document.getElementById("wmfroot")) {
            // on thread page, change size of editor div to double height
            var newCSS = "div#wpiprde.wpiprdeCSS { height: 300px !important; }";
            GM_addStyle(newCSS);
            // for non-richtext editor users, adjust row count in text area
            var newCSS = "textarea#wpiprdi { height: 300px !important; }";
            GM_addStyle(newCSS);
        } else if (document.body.id=="wpiprde") {
            // in the editor page (iframed), update CSS for fixed word wrap
            var newCSS = 'body#wpiprde.editable { width: 100% !important; }';
            GM_addStyle(newCSS);
        }
    } catch (ex) { }
}

// Add bottom anchor to page
function jumpToBottom() {
    try {
        var hash = document.location.hash.toString();
        if (hash=="#all") {
            var answerNode = document.getElementById("wpsfm"); // info bar
            if (!answerNode) answerNode = document.getElementById("wpcprie");
            var newNode = document.createElement("a");
            newNode.name = "bottomPage";
            answerNode.parentNode.insertBefore(newNode, answerNode);
            var gotoY = newNode.offsetTop;
            window.scrollTo(0, gotoY-(window.innerHeight-200));
        }
    } catch(ex) { } 
}

// Converts the last post's date to a link to this post in the forum overview page
function addLinkToLastPost(addLastLink, addReadTracker) {
    var i = 0, rows, row, tds, temp, threadurl = "", lastPost = ""; 
    try {
        if (document.getElementById("wlhd")) {
            // traverse table, extract link to post, add to last answer date
            rows = document.getElementsByTagName("tr");
            for (i=0; i<rows.length;i++) {
                row = rows[i];
                tds = row.getElementsByTagName("td");
                if (tds.length==5 || tds.length==6) {
                    // get thread URL
                    temp = row.innerHTML.match("\/support\/forum\/p\/[a-zA-Z0-9 ]*?\/thread.*?\"");
                    if (temp) {
                        if (addLastLink) {
                          threadUrl = temp[0].substring(0, temp[0].length-1).replace("&amp;","&");
                          lastPost = tds[tds.length-1];
                          // This wraps the link around the whole contents, which may also include 
                          // a <div> element. This may look weird in Firefox - but it works :-)
                          lastPost.innerHTML = "<a href=\"" + threadUrl + "#all\">" + lastPost.innerHTML + "</a>";
                        }
                        if (addReadTracker) {
                          // do the read tracker stuff
                          var threadId = temp[0].match("[tf]id=([a-fA-F0-9]{16})")[1];
                          var post_count = parseInt(tds[tds.length-2].innerHTML) + 1; // +original post
                          if (post_count<=GM_getValue(threadReadKey(threadId))) {
                            tds[tds.length-4].className += " wltbsCSS";
                          }
                        }
                    }
                }
            } 
        }
    } catch (ex) { }
}

// creates a key based on the thread ID
function threadReadKey(threadId) {
  return "THREAD_" + threadId;
}

// Returns true if the current page is a list of threads, 
// false if we're viewing a thread
function isThreadList() {
  return (URL.indexOf("/thread?")==-1);
}

// Returns number of posts on the current thread
function postCount() {
  //simplified: getElementsByClassName("wpcpdCSS", "div").length;
  var count = 0;
  var items = document.getElementsByTagName("div");
  var numItems = items.length;
  var classRegex = new RegExp("(^|\\s)" + "wpcpdCSS" + "(\\s|$)");
  for (var i=0; i<numItems; i++) {
    var className = items[i].className.replace("\r", " ").replace("\n", " ");
    if (classRegex.test(className)) count++;
  }
  return count;
}

// Workaround for the Chrome implementation of @include, applies to Firefox, too.
if (URL.startsWith("http://www.google.com/support/forum/p/")) {

    // Read the current user settings.
    setGHFConfig();

    // Inject the CSS styles  
    if (ghfConfig.thread_title_bold) {
        GM_addStyle('td.lcnCSS div:first-child a   { font-weight:bold; }');
        GM_addStyle('td.wltbsCSS div:first-child a { font-weight:normal; }');
        GM_addStyle('td.wltbgCSS div:first-child a { font-weight:normal; }');
    }
    if (1 == ghfConfig.votes_bold) {
        GM_addStyle('.wpcprsCSS { font-weight:bold;}');
    }

    GM_addStyle('table.lctCSS td div:first-child a { font-size: ' + ghfConfig.title_font_size + '; }');
    GM_addStyle('td.wltbsCSS { background-color: '      + ghfConfig.color_read_by_you + '; }');
    GM_addStyle('td.wltbsCSS div { background-color: '  + ghfConfig.color_read_by_you + '; }');
    GM_addStyle('td.wltbtcCSS { background-color: '     + ghfConfig.color_read_by_others + '; }');
    GM_addStyle('td.wltbtcCSS div { background-color: ' + ghfConfig.color_read_by_others + '; }');
    GM_addStyle('div.wltbtcCSS { background-color: ' + ghfConfig.color_read_by_others + '; }');

    if (ghfConfig.resize_editor) fixEditorTextField();

    window.addEventListener("load", jumpToBottom, false);
  
    if (ghfConfig.link_to_last || ghfConfig.read_tracker) 
        addLinkToLastPost(ghfConfig.link_to_last, ghfConfig.read_tracker);
  
    if (ghfConfig.show_side_links) {
        // inject the board links
        var languageCode = URL.match("[\?&]hl=([^&#]*)")[1];
        var forumName = URL.match("/forum\/p\/([^/]*)[/\?]")[1];
        var forumKey = forumName + "_" + languageCode;

        var cachedEntries = "";
        var cachedDate = GM_getValue(forumKey + "_cached", "");
        var currentDate = new Date();
    
        if (cachedDate != "") {
            // check if cache is still valid (24 hrs)
            if (currentDate - Date.parse(cachedDate)<(86400 * 1000)) {
                cachedEntries = GM_getValue(forumKey, "");
            }
        }
        if (cachedEntries != "") { 
            showSideLinks(cachedEntries);
        } else {
            // fetch sub-forum links from the forum homepage
            var theurl = "http://www.google.com/" + "support/forum/p/" + forumName + "?hl=" + languageCode;
            var request = new XMLHttpRequest();
            request.onreadystatechange = function() {
                if ((request.readyState == 4) && (request.status == 200)) {
                    var doc = request.responseText;
                    var entries = doc.match(/href=.*?lid=.*?</g);
                    var serialized = entries.join("\t");
                    GM_setValue(forumKey, serialized); // cache data
                    GM_setValue(forumKey + "_cached", currentDate.toString());
                    showSideLinks(serialized);
                }
            }
            request.open('GET', theurl, true);
            request.send(null);
        }
    }
    
    if (ghfConfig.read_tracker) {
      // determine whether we're on an overview page (multiple threads) or 
      // on a single thread (multiple posts)
      if (isThreadList()) {
        // traverse table and change class names for read posts
        // this is already done in addLinkToLastPost(), no need to do 
        // it separately.
      } else {
        // check the number of posts on this thread and store as read
        var threadId = URL.match("[tf]id=([a-fA-F0-9]{16})")[1];
        var post_count = postCount();
        GM_setValue(threadReadKey(threadId), post_count)
      }
    }
}

// EOF. 